07.2 精通自定义 View 之 绘图进阶——setShadowLayer 与阴影效果

返回自定义 View 目录

利用 layer-list 只能实现按钮的阴影效果,对于文字和图片都无法实现阴影效果,除了 layer-list,我们只能用自定义控件来实现阴影效果了,Paint 中有一个专门用来实现阴影效果的函数 setShadowLayer,我们先来看看这个函数实现的阴影效果图:

从效果图中可以看出 setShadowLayer 函数能够实现:

  • 定制阴影模糊程度
  • 定制阴影偏移距离
  • 清除阴影和显示阴影

7.2.1 setShadowLayer() 构造函数

1. 概述

1
public void setShadowLayer(float radius, float dx, float dy, int color)
  • float radius:意思是模糊半径,radius 越大越模糊,越小越清晰,但是如果 radius 设置为 0,则阴影消失不见。
  • float dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移。
  • float dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移。
  • int color:绘制阴影的画笔颜色,即阴影的颜色(对图片阴影无效)。

setShadowLayer 使用的是高斯模糊算法,高斯模糊的具体算法是:对于正在处理的每一个像素,取周围若干个像素的 RGB 值并且平均,然后这个平均值就是模糊处理过的像素。如果对图片中的所有像素都这么处理的话,处理完成的图片就会变得模糊。其中,所取周围像素的半径就是模糊半径。所以,模糊半径越大,所得平均像素与原始像素相差就越大,也就越模糊。

绘制阴影的画笔颜色为什么对图片无效?

从上面的效果图中可以看出,使用 setShadowLayer 所产生的阴影,对于文字和绘制的图形的阴影都是使用自定义的阴影画笔颜色来画的;而图片的阴影则是直接产生一张相同的图片,仅对阴影图片的边缘进行模糊。之所以生成一张相同的背景图片,是因为如果统一使用某一种颜色来做阴影可能会与图片的颜色相差很大,而且不协调,比如某张图片的色彩非常丰富,而阴影如果使用灰色来做,可能就会显得很突兀,所以为了解决这个问题,针对图片的阴影就不再是统一颜色了,而是复制出这张图片,把复制出的图片的边缘进行模糊,做为阴影。但这样又会引起一个问题,就是如果我们想把图片的阴影做成灰色怎么办?使用 setShadowLayer 自动生成阴影是没办法了,在下篇我们会具体来讲,如何给图片添加指定颜色的阴影。

注意:setShadowLayer 只有文字绘制阴影支持硬件加速,其它都不支持硬件加速,为了方便起见,需要在自定义控件中禁用硬件加速。

2. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
public class TestView extends View {
private Paint mPaint = new Paint();
private Bitmap mHeadBmp;
private Rect mRect;
private int mRadius = 1;
private int mDx = 10;
private int mDy = 10;
private boolean mSetShadow = true;
public TestView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
setLayerType(LAYER_TYPE_SOFTWARE, null);
mPaint.setColor(Color.GREEN);
mPaint.setTextSize(50);
mHeadBmp = BitmapFactory.decodeResource(getResources(), R.drawable.head);
mRect = new Rect();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mSetShadow) {
mPaint.setShadowLayer(mRadius, mDx, mDy, Color.GRAY);
} else {
mPaint.clearShadowLayer();
}
canvas.drawText("xian小涛",100,100, mPaint);
canvas.drawCircle(200,200,50, mPaint);
mRect.set(200,300,200 + mHeadBmp.getWidth(),300 + mHeadBmp.getHeight());
canvas.drawBitmap(mHeadBmp,null, mRect, mPaint);
}
public void changeRadius() {
mRadius++;
postInvalidate();
}
public void changeDx() {
mDx+=5;
postInvalidate();
}
public void changeDy() {
mDy+=5;
postInvalidate();
}
public void clearShadow(){
mSetShadow = false;
postInvalidate();
}
public void showShadow(){
mSetShadow = true;
postInvalidate();
}
}

使用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private TestView mView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
mView = findViewById(R.id.view);
findViewById(R.id.radius_btn).setOnClickListener(this);
findViewById(R.id.dx_btn).setOnClickListener(this);
findViewById(R.id.dy_btn).setOnClickListener(this);
findViewById(R.id.clear_btn).setOnClickListener(this);
findViewById(R.id.show_btn).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.radius_btn:
mView.changeRadius();
break;
case R.id.dx_btn:
mView.changeDx();
break;
case R.id.dy_btn:
mView.changeDy();
break;
case R.id.clear_btn:
mView.clearShadow();
break;
case R.id.show_btn:
mView.showShadow();
break;
}
}
}

7.2.2 清除阴影

清除阴影其实有两个方法,可以将 setShadowLayer 的 radius 的值设为 0,也可以使用专门的清除阴影的函数:

1
public void clearShadowLayer()

具体效果见上面例子。

7.2.3 示例:给文字添加阴影

从图中可以看到,TextView、Button、EditText 中的文字自定添加了阴影。而且对于 EditText 而言,新输入的文字依然有阴影效果。

setShadowLayer 是 API 1 就已经引入的函数,而且添加了 TextView 类 和 TextView 的派生类来支持阴影设置。TextView 的派生类如下图所示。

1. 通过 XML 属性添加阴影

1
2
3
4
5
6
<TextView
......
android:shadowRadius="3"
android:shadowDx="5"
android:shadowDy="5"
android:shadowColor="@android:color/darker_gray"/>

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="使用XML添加阴影效果"
android:shadowRadius="3"
android:shadowDx="5"
android:shadowDy="5"
android:shadowColor="@android:color/darker_gray"/>
<Button
android:id="@+id/radius_btn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="使用XML添加阴影效果"
android:shadowRadius="3"
android:shadowDx="5"
android:shadowDy="5"
android:shadowColor="@android:color/darker_gray"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="使用XML添加阴影效果"
android:shadowRadius="3"
android:shadowDx="5"
android:shadowDy="5"
android:shadowColor="@android:color/darker_gray"/>
</LinearLayout>

2. 代码实现

TextView 及其派生类都有一个 Paint.setShadowLayer 的同名方法:

1
public void setShadowLayer(float radius, float dx, float dy, int color)

示例:

1
2
TextView tv = findViewById(R.id.tv);
tv.setShadowLayer(3, 5, 5, Color.GRAY);